package com.zpz.framework.zpzoauth.common.encrypt;

import com.alibaba.fastjson.JSONObject;
import com.zpz.framework.zpzoauth.common.extend.StringExtend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * 签名
 * @author: zhangpanzhi
 * @date: 2019/3/13 14:03
 * @description:
 */
public class SignUtil {
    private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();
    public static final String FIELD_SIGN = "sign";

    public static void main(String[] args) {
//        try {
//            //client_sign_key =192006250b4c09247ec02edce69f6a2d
//            String key = "1509ecb5701e4e179ac764a031feead6";
//            JSONObject jsonObject1 = new JSONObject();
//            jsonObject1.put("client_id1", "1000021");
//            jsonObject1.put("user_name1", "test");
//            JSONObject jsonObject = new JSONObject();
//            jsonObject.put("client_id", "1000021");
//            jsonObject.put("user_name", "test");
//            jsonObject.put("pass_word", 12345.00);
//            jsonObject.put("nonce_str", generateNonceStr());
//            jsonObject.put("data", jsonObject1);
//            String jsonStr = jsonObject.toJSONString();
//            System.out.println("jsonStr:" + jsonStr);
//            //String  jsonStr="{\"actualFee\":1.00,\"discountFee\":0.17,\"orderNumber\":\"GK20190604175100000014\",\"password\":\"121212\",\"payNo\":\"1135846585440296960\",\"sign\":\"4F4418F772746B400C9C549FB4D1BB82\",\"totalFee\":0.83,\"userCode\":\"1134291546147168256\"}";
//            System.out.println("jsonStr:" + jsonStr);
//            //生成签名
//            String signStr = generateSigned(jsonStr, key);
//            System.out.println("signStr:" + signStr);
//            boolean isok = isSignatureValid(jsonStr, signStr, key);
//            System.out.println("isok:" + isok);
//        } catch (Exception ee) {
//            System.out.println(ee.getMessage());
//            ee.printStackTrace();
//        }
    }
    /**
     * Json格式字符串转换为Map
     *
     * @param strJson Json字符串
     * @return  Json数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> jsonToMap(String strJson) throws Exception {
        try {
            return  JsonUtil.jsonToMap(strJson);
        } catch (Exception ex) {
            logger.warn("Invalid Json, can not convert to map. Error message: {}.Json content: {}", ex.getMessage(), strJson);
            throw ex;
        }
    }

    /**
     * 将Map转换为JSON格式的字符串
     *
     * @param data Map类型数据
     * @return JSON格式的字符串
     * @throws Exception
     */
    public static String mapToJson(Map<String, String> data) throws Exception {
        Object json = JSONObject.toJSON(data);
        return json.toString();
    }

    /**
     * 生成签名字符串
     *
     * @param jsonStr  JSON格式数据 待签参数
     * @param key API密钥
     * @return 含有sign字段的json
     */
    public static String generateSigned(String jsonStr, String key) throws Exception {
        Map<String, String> data = jsonToMap(jsonStr);
        return generateSigned(data, key, SignType.MD5);
    }

    /**
     * 生成签名字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的json
     */
    public static String generateSigned(final Map<String, String> data, String key) throws Exception {
        return generateSigned(data, key, SignType.MD5);
    }

    /**
     * 生成签名字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的Json
     */
    public static String generateSigned(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        return sign;
    }
    public static String generateSigned8() {
        Random rand = new Random();
        int n = 100000+rand.nextInt(99900000);
        return n+"";
    }

    /**
     * 判断签名是否正确 签名为空直接返回false
     *
     * @param jsonStr JSON格式数据 待签参数
     * @param fieldSign 签名字符串
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String jsonStr,String fieldSign , String key) throws Exception {
        Map<String, String> data = jsonToMap(jsonStr);
        if (StringExtend.isNullOrEmpty(fieldSign)) {
            return false;
        }
        return generateSignature(data, key).equals(fieldSign);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param fieldSign 签名字符串
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data,String fieldSign, String key) throws Exception {
        return isSignatureValid(data, fieldSign, key, SignType.MD5);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data Map类型数据
     * @param fieldSign 签名字符串
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data,String fieldSign, String key, SignType signType) throws Exception {
        return generateSignature(data, key, signType).equals(fieldSign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(FIELD_SIGN)) {
                continue;
            }
            Object oo = data.get(k);
            String vlStr = oo.toString();
            if (vlStr.trim().length() > 0) {
                // 参数值为空，则不参与签名
                sb.append(k).append("=").append(vlStr.trim()).append("&");
            }
        }
        sb.append("client_sign_key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }
    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr(int count) {
        char[] nonceChars = new char[count];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }



    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }



    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳，单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

}
